package com.qg.wcsopcuaqj.taskPool;


import com.qg.wcsopcuaqj.dispatch.constant.QGSConstant;
import com.qg.wcsopcuaqj.dispatch.entity.RGV2Entity;
import com.qg.wcsopcuaqj.enums.rgv1.RGV1Plc2WcsVarEnum;
import com.qg.wcsopcuaqj.enums.rgv1.RGV1Wcs2PlcVarEnum;
import com.qg.wcsopcuaqj.enums.rgv2.RGV2Plc2WcsVarEnum;
import com.qg.wcsopcuaqj.enums.rgv2.RGV2Wcs2PlcVarEnum;
import com.qg.wcsopcuaqj.opcua.selfunion.Enum.PLCType;
import com.qg.wcsopcuaqj.opcua.selfunion.UAService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.Callable;

/**
 * @Author: 蔡翔
 * @Date: 2019/10/30 16:23
 * @Version 1.0
 */
public class RGV2Runnable<T> implements Callable<T> {
    private final static Logger logger = LoggerFactory.getLogger(RGV2Runnable.class);
    private UAService uaService;
    private RGV2Entity rgv2Entity;

    public RGV2Runnable(UAService uaService, RGV2Entity rgv2Entity){
        this.uaService = uaService;
        this.rgv2Entity = rgv2Entity;
    }
    public RGV2Runnable(){

    }
    //todo  1.把枚举类Plc2WcsVarEnum、Wcs2PlcVarEnum 录成实际位置；； 2.先事 写一个测试案例（controller）看看是否能够成功


    /**
     * desc: 入库和出库 共用这个方法，目前阶段 taskType 都是1
     * 程度： 现在的调度程度就是 1.没有做报警相关处理；2.当检测到报警相关变量不等于预定值的时候 是直接中断此任务的 并且没有做相关处理。
     * return
     *          -1     下发任务失败
     *          -2     opc操作失败、异常（人工处理 检查opc server、opc client）
     *          -3     当前小车正在执行任务，下发任务失败
     *          2      发生报警（plc自检没有通过）（人工处理 检查具体报警问题然后解决）
     *          1      此次任务成功完成
     *
     * */
    //todo 1.新增错误日志在每个err 后面 用logger 记录哪个阶段就行了。
    // 2. 思考 调度线程一个（每个阶段用 if (!Thread.currentThread().isInterrupted()){} 包装）、告警监控一个线程、心跳一个线程；
    // 当健康检查的两个线程发现问题 直接调用 调度线程future的cancel() 中断当前调度线程然后重置任务
    // 然后 本地肯定是要维护一个map的任务号就可以作为key，因为当前任务可以从plc 获取。
    // 参考：https://blog.csdn.net/jiaqingShareing/article/details/89076052
    // 第二种方式 就是 通过埋点的方式 检查告警 。。。

    @Override
    public T call() throws Exception {
        logger.info("=================  现在开始执行任务 : "+rgv2Entity.toString()+"  ==================");
        Integer res = 1;
        Integer begin = 1;

        Boolean free = Boolean.valueOf(getValue(RGV2Plc2WcsVarEnum.Free));
        if(!free){
            //繁忙情况
            Integer errCode = -3;
            logger.info("RGV2处于未空闲状态，请稍后再下发任务");
            logger.info("=================  任务结束 : "+rgv2Entity.toString()+"  ==================");
            return (T)errCode;
        }

        while (true){
            //判断当前堆垛机是否满足下发任务的条件
            if(judgeDDJStatusIsOk()){
                begin =2;
                if(issueTask()){
                    if(!setValue(RGV2Wcs2PlcVarEnum.STB,RGV2Wcs2PlcVarEnum.STB.getType(),1)){
                        res = -2;
                        logger.info("opc操作失败"+" --- "+"任务下发阶段 STEP01 WCS 给02表 STB => 1 ");
                        break;
                    }
                    logger.info("opc操作成功"+" --- "+"任务下发阶段 STEP01 WCS 给02表 STB => 1 ");


                    Integer plcJudgeReturn = waitingForTarget(RGV2Plc2WcsVarEnum.ACK,1,30000L);
                    //自检不通过的时候直接结束此任务，然后返回结果
                    if(plcJudgeReturn!=1){
                        res = plcJudgeReturn;
                        logger.info("opc操作失败 或者超时"+" --- "+"任务下发阶段 STEP03 WCS 监控 01表 ACK == 1 ");
                        break;
                    }
                    logger.info("opc操作成功"+" --- "+"任务下发阶段 STEP03 WCS 监控 01表 ACK == 1 ");


                    if(!setValue(RGV2Wcs2PlcVarEnum.STB,RGV2Wcs2PlcVarEnum.STB.getType(), 0)){
                        res = -2;
                        logger.info("opc操作失败"+" --- "+"任务下发阶段 STEP03 WCS 给02表 STB => 0 ");
                        break;
                    }
                    logger.info("opc操作成功"+" --- "+"任务下发阶段 STEP03 WCS 给02表 STB => 0 ");


                    //到此 任务下发完成，，，下面就是监控堆垛机完成任务
                    //Integer judgeSTB = waitingForTarget(RGV2Plc2WcsVarEnum.STB, 1, QGSConstant.deadLineTime);
                    Integer judgeSTB = waitingForKeepTarget(RGV2Plc2WcsVarEnum.STB, 1,QGSConstant.deadLineTime);
                    if(judgeSTB != 1){
                        res = judgeSTB;
                        logger.info("opc操作失败 或者超时"+" --- "+"任务反馈阶段 STEP02 WCS 监控 01表 STB == 1 ");
                        break;
                    }
                    logger.info("opc操作成功"+" --- "+"任务反馈阶段 STEP02 WCS 监控 01表 STB == 1 ");

                    //无论任务成功或者失败，taskFinish 都是为true的。。
                    Integer judgeTaskFinish = waitingForTarget(RGV2Plc2WcsVarEnum.TaskFinish, true, QGSConstant.deadLineTime);
                    if(judgeTaskFinish != 1){
                        res = judgeTaskFinish;
                        logger.info("opc操作失败 或者超时"+" --- "+"任务反馈阶段 STEP02 WCS 监控 01表 TaskFinish == true ");
                        break;
                    }
                    logger.info("opc操作成功"+" --- "+"任务反馈阶段 STEP02 WCS 监控 01表 TaskFinish == true ");

                    //当taskFinish 为true的时候，这个时候再来判断这个任务是成功还是失败了。
                    Boolean judgeTaskFault = Boolean.valueOf(getValue(RGV2Plc2WcsVarEnum.TaskFault));
                    if(judgeTaskFault){
                        res = -1;
                        logger.info("opc操作失败 或者 任务失败 或者 人工中断此任务"+" --- "+"任务反馈阶段 STEP02 WCS 监控 01表 TaskFault == true ");
                        break;
                    }
                    logger.info("opc操作成功"+" --- "+"任务反馈阶段 STEP02 WCS 监控 01表 TaskFault == false ( 就代表任务成功完成 ) ");


                    if(!setValue(RGV2Wcs2PlcVarEnum.ACK,RGV2Wcs2PlcVarEnum.ACK.getType(), 1)){
                        res = -2;
                        logger.info("opc操作失败"+" --- "+"任务反馈阶段 STEP02 WCS 给01表 ACK => 1 ");
                        break;
                    }
                    logger.info("opc操作成功"+" --- "+"任务反馈阶段 STEP02 WCS 给01表 ACK => 1 ");


                    Integer reset = waitingForTarget(RGV2Plc2WcsVarEnum.STB, 0, QGSConstant.deadLineTime);
                    if(reset != 1){
                        res = reset;
                        logger.info("opc操作失败"+" --- "+"任务反馈阶段 STEP04 WCS 监控 01表 STP == 0 ");
                        break;
                    }
                    logger.info("opc操作成功"+" --- "+"任务反馈阶段 STEP04 WCS 监控 01表 STP == 0 ");


                    if(!setValue(RGV2Wcs2PlcVarEnum.ACK,RGV2Wcs2PlcVarEnum.ACK.getType(), 0)){
                        res = -2;
                        logger.info("opc操作失败"+" --- "+"任务反馈阶段 STEP04 WCS 给 02表 ACK => 0 ");
                        break;
                    }
                    logger.info("opc操作成功"+" --- "+"任务反馈阶段 STEP04 WCS 给 02表 ACK => 0 ");

                }else {
                    res = -1;
                    logger.info("下发任务失败");
                    break;
                }
            }else {
                logger.info("堆垛机暂时还不满足条件");
                Thread.sleep(1000);
                continue;
            }

            if(begin ==1){
                continue;
            }else {
                break;
            }
        }

        resetSTB();
        logger.info("=================  任务结束 : "+rgv2Entity.toString()+"  ==================");
        return (T)res;
        //return (T(1));
    }

    /**
     * desc: 下发任务给电控
     * return：
     *      true  成功
     *      false 失败
     * */
    private Boolean issueTask() throws Exception {
        Boolean pickRow = setValue(RGV2Wcs2PlcVarEnum.PickRow,RGV2Wcs2PlcVarEnum.PickRow.getType(), rgv2Entity.getPickRow());

        Boolean sendRow = setValue(RGV2Wcs2PlcVarEnum.SendRow,RGV2Wcs2PlcVarEnum.SendRow.getType(), rgv2Entity.getSendRow());
        //todo 数据类型，待新增 ，然后改
        Boolean taskNo = setValue(RGV2Wcs2PlcVarEnum.TaskNo,RGV2Wcs2PlcVarEnum.TaskNo.getType(), rgv2Entity.getTaskNo());
        Boolean taskType = setValue(RGV2Wcs2PlcVarEnum.TaskType,RGV2Wcs2PlcVarEnum.TaskType.getType(), rgv2Entity.getTaskType());
//        Boolean ideNumber = setValue(RGV2Wcs2PlcVarEnum.IdeNumber,RGV2Wcs2PlcVarEnum.IdeNumber.getType(), rgv2Entity.getIdeNumber());
//        Boolean processNumber = setValue(RGV2Wcs2PlcVarEnum.ProcessNumber,RGV2Wcs2PlcVarEnum.ProcessNumber.getType(), rgv2Entity.getProcessNumber());
        Boolean ideNumber = setValue(RGV2Wcs2PlcVarEnum.IdeNumber,RGV2Wcs2PlcVarEnum.IdeNumber.getType(), rgv2Entity.getIdeNumber());
        Boolean processNumber = setValue(RGV2Wcs2PlcVarEnum.ProcessNumber,RGV2Wcs2PlcVarEnum.ProcessNumber.getType(), rgv2Entity.getProcessNumber());


        if(pickRow &&  taskNo && sendRow  && taskType && ideNumber && processNumber){
            logger.info("写入任务信息成功，pickRow:"+pickRow+",sendRow:"+sendRow+",taskNo:"+taskNo+",taskType:"+taskType+",ideNumber:"+ideNumber+",processNumber:"+processNumber);
            return true;
        }else {
            logger.info("写入任务信息失败");
            return false;
        }
    }



    /**
     * desc: 检测是否发生报警
     * return：
     *      true  发生了报警
     *      false 没有发生报警
     * */
    private Boolean alarmDetect() throws Exception {
        //todo alarm 其实需要实时检测，这样的话就不能写在调度程序里面了。只能写在第三方检测 线程里面。
        // 但是这里alarm 是分两种的：1.一种是不影响任务  2.影响任务（任务就进行不下去了 需要结束任务，，这种情况就不适用第三方检测程序了。）

        return Boolean.valueOf(getValue(RGV2Plc2WcsVarEnum.Alarm));
    }

    /**
     * desc: PLC自检  是否可执行 或者报警 (是一个 “或” 的操作 一旦有一个条件满足都会跳出循环)
     * return：
     *      1  可执行
     *      2  发生报警
     *      -2 操作opc失败
     * status：not work（待定，这个暂时用不到）
     * */
    private Integer plcJudgeIsOk() {
        Integer res = 0;
        try {
            while (true){
                Boolean aBoolean = alarmDetect();
                Integer value = Integer.valueOf(getValue(RGV2Plc2WcsVarEnum.ACK));
                if(aBoolean){
                    res = 2;
                    break;
                }
                if(value == 1){
                    res = 1;
                    break;
                }

                Thread.sleep(500);
            }

        }catch (Exception e){
            logger.info("plcJudgeIsOk 方法中出现读取opc 异常");
            res = -2;
        }
        return res;

    }

    /**
     * desc: 判断当前堆垛机是否满足下发任务的条件
     * return：
     *      true  满足
     *      false 不满足
     * */
    private Boolean judgeDDJStatusIsOk() throws Exception {
        //下发任务前线  重置STP == 0 ，防止 任务异常 STP 还是为1  未被重置为0，网络问题 或者 人工问题。
        resetSTB();

        //target == true
        Boolean rgvStatus = Boolean.valueOf(getValue(RGV2Plc2WcsVarEnum.RGV2));
        //target == true
        Boolean free = Boolean.valueOf(getValue(RGV2Plc2WcsVarEnum.Free));
        //target == true
        Boolean online = Boolean.valueOf(getValue(RGV2Plc2WcsVarEnum.Online));
        //target == false
        Boolean alarm = Boolean.valueOf(getValue(RGV2Plc2WcsVarEnum.Alarm));

        //target == 0
//        Integer stb = Integer.valueOf(getValue(RGV2Wcs2PlcVarEnum.STB));

        //target == 0
        Integer ack = Integer.valueOf(getValue(RGV2Plc2WcsVarEnum.ACK));
        //logger.info("判断是否满足下发条件： rgvStatus:"+rgvStatus+"  free:"+free+"  online:"+online+"  alarm:"+alarm+"  stb:"+stb+"  ack:"+ack);
        logger.info("判断是否满足下发条件： rgvStatus:"+rgvStatus+"  free:"+free+"  online:"+online+"  alarm:" +alarm+ "  ack:"+ack);
        if(online && rgvStatus && free && !alarm && ack == 0){
            logger.info("堆垛机 满足 下发任务条件");
            return true;
        }else {
            logger.info("堆垛机 不满足 下发任务条件");
            return false;
        }
    }

    private void resetSTB() throws Exception {
        logger.info("重置 STP ");
        Boolean ideNumber = setValue(RGV2Wcs2PlcVarEnum.STB,RGV2Wcs2PlcVarEnum.STB.getType(), 0);
    }

    private String getValue(RGV2Plc2WcsVarEnum var) throws Exception {
        return uaService.getValue(var.getNamespace(), var.getIdentity(), QGSConstant.plcName).getValue().getValue().toString();
    }
    private String getValue(RGV2Wcs2PlcVarEnum var) throws Exception {
        return uaService.getValue(var.getNamespace(), var.getIdentity(), QGSConstant.plcName).getValue().getValue().toString();
    }

//    private Boolean setValue(Wcs2PlcVarEnum var,Object newValue) throws Exception {
//        //return uaService.setValue(var.getNamespace(), var.getIdentity(),newValue, QGSConstant.plcName);
//        return uaService.setValue(var.getNamespace(), var.getIdentity(), PLCType.valueOf("QShort").convertType(newValue), QGSConstant.plcName);
//    }
//    private Boolean setValue(Plc2WcsVarEnum var,Object newValue) throws Exception {
//        //return uaService.setValue(var.getNamespace(), var.getIdentity(),newValue, QGSConstant.plcName);
//        return uaService.setValue(var.getNamespace(), var.getIdentity(),PLCType.valueOf("QShort").convertType(newValue), QGSConstant.plcName);
//    }

    private Boolean setValue(RGV2Wcs2PlcVarEnum var,String dataType,Object newValue) throws Exception {
        return uaService.setValue(
                var.getNamespace(),
                var.getIdentity(),
                PLCType.valueOf(dataType).convertType(newValue),
                QGSConstant.plcName);
    }
    private Boolean setValue(RGV2Plc2WcsVarEnum var,String dataType,Object newValue) throws Exception {
        return uaService.setValue(
                var.getNamespace(),
                var.getIdentity(),
                PLCType.valueOf(dataType).convertType(newValue),
                QGSConstant.plcName
        );
    }


    //-1 超时退出； 2 opc 访问异常；1 成功
    private Integer waitingForTarget(RGV2Plc2WcsVarEnum var,Integer targetValue,Long deadLine) {
        Integer res = 0;
        long start = System.currentTimeMillis();
        while (true){
            try {
                long end = System.currentTimeMillis();
                if((end-start)>deadLine){
                    res = -1;
                    break;
                }
                //先判断一下是否发生告警
//                if(alarmDetect()){
//                    res = -2;
//                    break;
//                }

                Integer now = Integer.valueOf(getValue(var));
                if(targetValue.equals(now)){
                    res = 1;
                    break;
                }
                Thread.sleep(200);
            }catch (Exception e){
                logger.info("opc 访问异常。");
                res = 2;
                break;
            }
        }
        return res;
    }
    private Integer waitingForKeepTarget(RGV2Plc2WcsVarEnum var,Integer targetValue,Long deadLine) {
        Integer res = 0;
        long start = System.currentTimeMillis();
        while (true){
            try {
                long end = System.currentTimeMillis();

                //先判断一下是否发生告警
//                if(alarmDetect()){
//                    res = -2;
//                    break;
//                }
                Integer now = Integer.valueOf(getValue(var));

                if(targetValue.equals(now) && ((end-start)>2000)){
                    res = 1;
                    break;
                }

                if((end-start)>deadLine){
                    res = -1;
                    break;
                }
                Thread.sleep(200);
            }catch (Exception e){
                logger.info("opc 访问异常。");
                res = 2;
                break;
            }
        }
        return res;
    }

    private Integer waitingForTarget(RGV2Plc2WcsVarEnum var,Boolean targetValue,Long deadLine) {
        Integer res = 0;
        long start = System.currentTimeMillis();
        while (true){
            try {
                long end = System.currentTimeMillis();
                if((end-start)>deadLine){
                    res = -1;
                    break;
                }

                //先判断一下是否发生告警
//                if(alarmDetect()){
//                    res = -2;
//                    break;
//                }

                Boolean now = Boolean.valueOf(getValue(var));

                if(targetValue.equals(now)){
                    res = 1;
                    break;
                }
                Thread.sleep(200);
            }catch (Exception e){
                logger.info("opc 访问异常。");
                res = 2;
                break;
            }
        }
        return res;
    }
}
